iT邦幫忙

DAY 8
1

以Asp .Net MVC 5 為基礎,建立自己的程式開發框架系列 第 8

框架簡化建立AutoMapper對應的設定

  • 分享至 

  • xImage
  •  

在上一篇我們介紹了AutoMapper的設定和用法,使用起來肯定比自己手動做左邊倒到右邊還要簡單。

不過AutoMapper也不是沒有它自己的問題,最麻煩的地方在於設定Entity和Class之間的對應。這一篇要探討的就是,如何透過框架來減少這方面的設定。

同步發表於我的部落格:http://alantsai2007.blogspot.tw/2014/09/BuildYourOwnApplicationFrameworkOnMvc-08-AutoMapperConfigEasier.html

框架思路

我們先來思考一下我們會如何達到簡化設定對應邏輯,然後在開始開發。

首先,其實AutoMapper本身有所謂的Profile,可以透過Profile來設定Entity和ViewModel之間的對應。不過我個人比較傾向於Entity和ViewModel的對應邏輯是能夠簡單看到並且是在一起,換句話說,如果能夠在ViewModel定義好和Entity的對應關係不是很好,因為只要一找到ViewModel,馬上就知道它和Entity的關係。

有了這個概念,我們就可以來看一下我們如何透過Interface來達到這個效果。

Interface的定義

我們要提供兩種定義的方式:

  1. IMapFrom<T> - T表示這個ViewModel對應的Entity
  2. IHaveCustomMapping - 表示這個ViewModel要自己對應Entity和設定自己的邏輯

因此看起來會是:

interface的Class diagram

然後實際的C#程式碼是:

/// <summary>
/// 設定ViewModel要對應的Model。
/// 這個用預設的Convention來對應
/// </summary>
/// <typeparam name="T">要被對應到的Type</typeparam>
public interface IMapFrom<T>
{
}
 
/// <summary>
/// 設定ViewModel要對應的Model
/// 如果需要客制AutoMapper的邏輯,讓ViewModel實作此Interface
/// </summary>
public interface IHaveCustomMapping
{
    /// <summary>
    /// 設定自定義的Mapping邏輯
    /// </summary>
    /// <param name="configuration">Automapper的Config物件</param>
    void CreateMappings(IConfiguration configuration);
}

使用兩個interface的差異

使用IMapFrom<T>

我們先看一下上一篇我們IndexViewModel本來的用法:

Mapper.CreateMap<Post, IndexViewModel>();
var projectIQueryable = (db.Post.Project().To<IndexViewModel>()).ToList();

如果改用成我們的interface,會變成:

// ViewModel加上interface
public class IndexViewModel : IMapFrom<Post>
 
....
// 在實際呼叫的時候,會和之前一樣,只是不需要呼叫CreatMap
 
var projectIQueryable = (db.Post.Project().To<IndexViewModel>()).ToList();

使用IHaveCustomMapping

這個是假設有特殊的對應邏輯才在呼叫,使用上會是:

public class IndexViewModel : IHaveCustomMapping
{
   // properties
 
    public void CreateMappings(IConfiguration configuration)
    {
        configuration.CreateMap<Post, IndexViewModel>();
    }
}

可以看到,AutoMapper的IConfiguration會被傳進來,這時候就可以手動設定對應邏輯。

到這邊為止,我們interface的定義和使用就完成了,不過接下來我們還需要讓這兩個interface實際有作用,要不然是沒有效果。

在系統啟動的時候註冊AutoMapper對應

當我們用了interface把這些ViewModel的對應都定義好了之後,我們希望在系統啟動了之後,讀出所有設定過這兩種interface的ViewModel,並且作出對應的AutoMapper設定。

我們首先寫好使用這兩個interface的邏輯:

顯示取得所有實作這兩個interface的type:

/// <summary>
/// 註冊有設定AutoMapper的viewmodel
/// </summary>
public class AutoMapperConfig : IRunAtStartup
{
  /// <summary>
 /// 要執行的邏輯
  /// </summary>
    public void Execute()
   {
       var typeOfIHaveCustomMapping = typeof(IHaveCustomMapping);
      var typeOfIMapFrom = typeof(IMapFrom<>);
   
        // Type 符合 IHaveCustomMapping 和 IMapFrom 的 predicate方法
      // 這個predicate 的條件和下面個別mapping的第一個條件是一致的。
       Func<Type, bool> predicate = (t => typeOfIHaveCustomMapping.IsAssignableFrom(t) // 找到符合IHaveCustomMapping
                  || t.GetInterfaces().Where(i => i.IsGenericType && i.GetGenericTypeDefinition() == typeOfIMapFrom).Any()); // 找到符合IMapFrom<>
    
        var types = AssemblyTypes.GetAssemblyFromDirectory(assembly => assembly.GetExportedTypes().Where(predicate).Any())     // 選擇要讀進來的Assembly - 只有符合IHaveCustomMapping 和 IMapFrom才讀
          // 把讀進來的Assembly取出裡面符合兩個interface的Type
              .SelectMany(x => x.GetExportedTypes()
                .Where(predicate)).ToList();
     
        LoadStandardMappings(types);
     
        LoadCustomMappings(types);
  }
    
}

在來針對兩個不同的interface呼叫不同的mapping邏輯:

/// <summary>
/// 註冊如果使用是自定義邏輯的Mapping
/// </summary>
/// <param name="types">可能符合的Type</param>
private static void LoadCustomMappings(IEnumerable<Type> types)
{
  var maps = (from t in types
             from i in t.GetInterfaces()
             where typeof(IHaveCustomMapping).IsAssignableFrom(t) &&
                 !t.IsAbstract &&
                    !t.IsInterface
              select (IHaveCustomMapping)Activator.CreateInstance(t)).ToArray();
 
  foreach (var map in maps)
   {
       map.CreateMappings(AutoMapper.Mapper.Configuration);
    }
}
 
/// <summary>
/// Loads the standard mappings.
/// </summary>
/// <param name="types">The types.</param>
private static void LoadStandardMappings(IEnumerable<Type> types)
{
 var maps = (from t in types
             from i in t.GetInterfaces()
             where i.IsGenericType && i.GetGenericTypeDefinition() == typeof(IMapFrom<>) &&
                    !t.IsAbstract &&
                    !t.IsInterface
              select new
              {
                   Source = i.GetGenericArguments()[0],
                    Destination = t
             }).ToArray();
 
   foreach (var map in maps)
   {
       AutoMapper.Mapper.CreateMap(map.Source, map.Destination);
   }
}

Task Module

在上面的部分,如果注意看的話,AutoMapperConfig : IRunAtStartup。而IRunAtStartup其實屬於我們框架的Task系統。以IRunAtStartUp 來說,表示實作這個interface的Class將會在系統啟動的時候執行。

因此我們先設定這個Task的Autofac Module:

/// <summary>
/// Autofac用來註冊Task相關的服務
/// </summary>
public class TaskModule : Autofac.Module
{
    /// <summary>
    /// Override to add registrations to the container.
    /// </summary>
    /// <param name="builder">The builder through which components can be
    /// registered.</param>
    /// <remarks>
    /// Note that the ContainerBuilder parameter is unique to this module.
    /// </remarks>
    protected override void Load(Autofac.ContainerBuilder builder)
    {
        var assemblies = Assembly.GetExecutingAssembly();           
        builder.RegisterAssemblyTypes(assemblies).As<IRunAtStartup>();
    }
}

然後在Global.asax的地方註冊這個Module:

// global.asax Application_Start
...
Builder.RegisterModule<TaskModule>();
..

最後,因為這個IRunAtStartup屬於系統啟動的時候執行,因此在同樣global.asax裡面的Application_Start,我們就會:

// global.asax Application_Start
...
 
var container = builder.Build();
 
DependencyResolver.SetResolver(new AutofacDependencyResolver(container));
 
// 執行IRunAtStartUp的實作物件
using (var scope = container.BeginLifetimeScope())
{
    var runAtStartUpTasks = scope.Resolve<IEnumerable<IRunAtStartup>>();
 
    foreach (var item in runAtStartUpTasks)
    {
        item.Execute();
    }
}

這樣我們有設定的那兩種interface Mapping的AutoMapper定義就會有效果了。

結語

在這一篇我們把AutoMapper的對應設定邏輯利用2種interface把它抽到了和ViewModel一起定義。這樣的好處是我們只要看到ViewModel,就會知道他和那些Entity有對應關係。

希望透過這一篇,讓在使用AutoMapper的時候能夠更簡單,並且更容易使用。

在下一篇,我們來看如何透過Unit of Work和Repository Pattern把DB的溝通抽出來。


上一篇
AutoMapper 介紹 - 簡單化Entity和ViewModel之間的轉換
下一篇
用Repository Pattern抽離對Entity Framework的依賴
系列文
以Asp .Net MVC 5 為基礎,建立自己的程式開發框架30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言